<!DOCTYPE html>
<html lang="zh-cn">
<head>
  <title>框架扩展 - 为企业级框架和应用而生</title>
  <meta charset="utf-8">
  <meta name="description" content="index.description">
  <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  <link rel="icon" href="/images/favicon.png" type="image/x-icon">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/docsearch.js/2/docsearch.min.css" />
<link rel="stylesheet" href="/css/index.css">

    <script>
    !function(t,e,a,r,c){t.TracertCmdCache=t.TracertCmdCache||[],t[c]=window[c]||
      {_isInit:!0,call:function(){t.TracertCmdCache.push(arguments)},
      start:function(t){this.call('start',t)}},t[c].l=new Date;
      var n=e.createElement(a),s=e.getElementsByTagName(a)[0];
      n.async=!0,n.src=r,s.parentNode.insertBefore(n,s)}
    (window,document,'script','https://tracert.alipay.com/tracert.js','Tracert');
      Tracert.start({
        plugins: [ 'BucName' ],
        spmAPos: 'a454',
        spmBPos: 'b4893',
      });
    </script>
  
</head>
<body>
  <div class="nav" >
  <header>
    <a href="/zh-cn/" class="nav-logo leftpadding" alt="egg"><img src="https://zos.alipayobjects.com/rmsportal/VTcUYAaoKqXyHJbLAPyF.svg"></a>
    <ul class="nav-item">
      <li>
        <form id="search-form">
          <input type="text" id="search-query" class="search-query st-default-search-input">
        </form>
      </li>
      <li><a href="/zh-cn/intro/" alt="指南">指南</a></li><li><a href="/api/" alt="API">API</a></li><li><a href="/zh-cn/tutorials/index.html" alt="教程">教程</a></li><li><a href="https://github.com/search?q=topic%3Aegg-plugin&type=Repositories" alt="插件">插件</a></li><li><a href="https://github.com/eggjs/egg/releases" alt="发布日志">发布日志</a></li>
      
      
        <li class="translations">
          <a class="nav-link">Translations</a>
          <span class="arrow"></span><ul id="dropdownContent" class="dropdown-content"><li><a id="en" href="/en/basics/extend.html" >English</a></li><li><a id="zh-cn" href="/zh-cn/basics/extend.html" style="color: #22ab28">中文</a></li></ul>
        </li>
      
      <li><iframe src="https://ghbtns.com/github-btn.html?user=eggjs&repo=egg&type=star&count=true" frameborder="0" scrolling="0" width="150px" height="20px"></iframe></li>
    </ul>
    <a id="mobileTrigger" href="#" class="mobile-trigger">
      <ul>
        <li></li>
        <li></li>
        <li></li>
      </ul>
    </a>
  </header>
</div>
  <div id="container" class="container">
    <div class="page-main">
  <article class="markdown-body">
    <h1>框架扩展</h1>
    <p>框架提供了多种扩展点扩展自身的功能：</p>
<ul>
<li>Application</li>
<li>Context</li>
<li>Request</li>
<li>Response</li>
<li>Helper</li>
</ul>
<p>在开发中，我们既可以使用已有的扩展 API 来方便开发，也可以对以上对象进行自定义扩展，进一步加强框架的功能。</p>
<h2 id="application"><a class="markdown-anchor" href="#application">#</a> Application</h2>
<p><code>app</code> 对象指的是 Koa 的全局应用对象，全局只有一个，在应用启动时被创建。</p>
<h3 id="访问方式"><a class="markdown-anchor" href="#访问方式">#</a> 访问方式</h3>
<ul>
<li>
<p><code>ctx.app</code></p>
</li>
<li>
<p>Controller，Middleware，Helper，Service 中都可以通过 <code>this.app</code> 访问到 Application 对象，例如 <code>this.app.config</code> 访问配置对象。</p>
</li>
<li>
<p>在 <code>app.js</code> 中 <code>app</code> 对象会作为第一个参数注入到入口函数中</p>
 <figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="params">app</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">// 使用 app 对象</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
</li>
</ul>
<h3 id="扩展方式"><a class="markdown-anchor" href="#扩展方式">#</a> 扩展方式</h3>
<p>框架会把 <code>app/extend/application.js</code> 中定义的对象与 Koa Application 的 prototype 对象进行合并，在应用启动时会基于扩展后的 prototype 生成 <code>app</code> 对象。</p>
<h4 id="方法扩展"><a class="markdown-anchor" href="#方法扩展">#</a> 方法扩展</h4>
<p>例如，我们要增加一个 <code>app.foo()</code> 方法：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app/extend/application.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  foo(param) &#123;</span><br><span class="line">    <span class="comment">// this 就是 app 对象，在其中可以调用 app 上的其他方法，或访问属性</span></span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h4 id="属性扩展"><a class="markdown-anchor" href="#属性扩展">#</a> 属性扩展</h4>
<p>一般来说属性的计算只需要进行一次，那么一定要实现缓存，否则在多次访问属性时会计算多次，这样会降低应用性能。</p>
<p>推荐的方式是使用 Symbol + Getter 的模式。</p>
<p>例如，增加一个 <code>app.bar</code> 属性 Getter：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app/extend/application.js</span></span><br><span class="line"><span class="keyword">const</span> BAR = <span class="built_in">Symbol</span>(<span class="string">'Application#bar'</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  get bar() &#123;</span><br><span class="line">    <span class="comment">// this 就是 app 对象，在其中可以调用 app 上的其他方法，或访问属性</span></span><br><span class="line">    <span class="keyword">if</span> (!<span class="keyword">this</span>[BAR]) &#123;</span><br><span class="line">      <span class="comment">// 实际情况肯定更复杂</span></span><br><span class="line">      <span class="keyword">this</span>[BAR] = <span class="keyword">this</span>.config.xx + <span class="keyword">this</span>.config.yy;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">this</span>[BAR];</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h2 id="context"><a class="markdown-anchor" href="#context">#</a> Context</h2>
<p>Context 指的是 Koa 的请求上下文，这是 <strong>请求级别</strong> 的对象，每次请求生成一个 Context 实例，通常我们也简写成 <code>ctx</code>。在所有的文档中，Context 和 <code>ctx</code> 都是指 Koa 的上下文对象。</p>
<h3 id="访问方式-2"><a class="markdown-anchor" href="#访问方式-2">#</a> 访问方式</h3>
<ul>
<li>middleware 中 <code>this</code> 就是 ctx，例如 <code>this.cookies.get('foo')</code>。</li>
<li>controller 有两种写法，类的写法通过 <code>this.ctx</code>，方法的写法直接通过 <code>ctx</code> 入参。</li>
<li>helper，service 中的 this 指向 helper，service 对象本身，使用 <code>this.ctx</code> 访问 context 对象，例如 <code>this.ctx.cookies.get('foo')</code>。</li>
</ul>
<h3 id="扩展方式-2"><a class="markdown-anchor" href="#扩展方式-2">#</a> 扩展方式</h3>
<p>框架会把 <code>app/extend/context.js</code> 中定义的对象与 Koa Context 的 prototype 对象进行合并，在处理请求时会基于扩展后的 prototype 生成 ctx 对象。</p>
<h4 id="方法扩展-2"><a class="markdown-anchor" href="#方法扩展-2">#</a> 方法扩展</h4>
<p>例如，我们要增加一个 <code>ctx.foo()</code> 方法：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app/extend/context.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  foo(param) &#123;</span><br><span class="line">    <span class="comment">// this 就是 ctx 对象，在其中可以调用 ctx 上的其他方法，或访问属性</span></span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h4 id="属性扩展-2"><a class="markdown-anchor" href="#属性扩展-2">#</a> 属性扩展</h4>
<p>一般来说属性的计算在同一次请求中只需要进行一次，那么一定要实现缓存，否则在同一次请求中多次访问属性时会计算多次，这样会降低应用性能。</p>
<p>推荐的方式是使用 Symbol + Getter 的模式。</p>
<p>例如，增加一个 <code>ctx.bar</code> 属性 Getter：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app/extend/context.js</span></span><br><span class="line"><span class="keyword">const</span> BAR = <span class="built_in">Symbol</span>(<span class="string">'Context#bar'</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  get bar() &#123;</span><br><span class="line">    <span class="comment">// this 就是 ctx 对象，在其中可以调用 ctx 上的其他方法，或访问属性</span></span><br><span class="line">    <span class="keyword">if</span> (!<span class="keyword">this</span>[BAR]) &#123;</span><br><span class="line">      <span class="comment">// 例如，从 header 中获取，实际情况肯定更复杂</span></span><br><span class="line">      <span class="keyword">this</span>[BAR] = <span class="keyword">this</span>.get(<span class="string">'x-bar'</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">this</span>[BAR];</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h2 id="request"><a class="markdown-anchor" href="#request">#</a> Request</h2>
<p>Request 对象和 Koa 的 Request 对象相同，是 <strong>请求级别</strong> 的对象，它提供了大量请求相关的属性和方法供使用。</p>
<h3 id="访问方式-3"><a class="markdown-anchor" href="#访问方式-3">#</a> 访问方式</h3>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">ctx.request</span><br></pre></td></tr></table></figure>
<p><code>ctx</code> 上的很多属性和方法都被代理到 <code>request</code> 对象上，对于这些属性和方法使用 <code>ctx</code> 和使用 <code>request</code> 去访问它们是等价的，例如 <code>ctx.url === ctx.request.url</code>。</p>
<p>Koa 内置的代理 <code>request</code> 的属性和方法列表：<a href="http://koajs.com/#request-aliases" target="_blank" rel="noopener">Koa - Request aliases</a></p>
<h3 id="扩展方式-3"><a class="markdown-anchor" href="#扩展方式-3">#</a> 扩展方式</h3>
<p>框架会把 <code>app/extend/request.js</code> 中定义的对象与内置 <code>request</code> 的 prototype 对象进行合并，在处理请求时会基于扩展后的 prototype 生成 <code>request</code> 对象。</p>
<p>例如，增加一个 <code>request.foo</code> 属性 Getter：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app/extend/request.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  get foo() &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">this</span>.get(<span class="string">'x-request-foo'</span>);</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h2 id="response"><a class="markdown-anchor" href="#response">#</a> Response</h2>
<p>Response 对象和 Koa 的 Response 对象相同，是 <strong>请求级别</strong> 的对象，它提供了大量响应相关的属性和方法供使用。</p>
<h3 id="访问方式-4"><a class="markdown-anchor" href="#访问方式-4">#</a> 访问方式</h3>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">ctx.response</span><br></pre></td></tr></table></figure>
<p>ctx 上的很多属性和方法都被代理到 <code>response</code> 对象上，对于这些属性和方法使用 <code>ctx</code> 和使用 <code>response</code> 去访问它们是等价的，例如 <code>ctx.status = 404</code> 和 <code>ctx.response.status = 404</code> 是等价的。</p>
<p>Koa 内置的代理 <code>response</code> 的属性和方法列表：<a href="http://koajs.com/#response-aliases" target="_blank" rel="noopener">Koa Response aliases</a></p>
<h3 id="扩展方式-4"><a class="markdown-anchor" href="#扩展方式-4">#</a> 扩展方式</h3>
<p>框架会把 <code>app/extend/response.js</code> 中定义的对象与内置 <code>response</code> 的 prototype 对象进行合并，在处理请求时会基于扩展后的 prototype 生成 <code>response</code> 对象。</p>
<p>例如，增加一个 <code>response.foo</code> 属性 setter：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app/extend/response.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  set foo(value) &#123;</span><br><span class="line">    <span class="keyword">this</span>.set(<span class="string">'x-response-foo'</span>, value);</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>就可以这样使用啦：<code>this.response.foo = 'bar';</code></p>
<h2 id="helper"><a class="markdown-anchor" href="#helper">#</a> Helper</h2>
<p>Helper 函数用来提供一些实用的 utility 函数。</p>
<p>它的作用在于我们可以将一些常用的动作抽离在 helper.js 里面成为一个独立的函数，这样可以用 JavaScript 来写复杂的逻辑，避免逻辑分散各处。另外还有一个好处是 Helper 这样一个简单的函数，可以让我们更容易编写测试用例。</p>
<p>框架内置了一些常用的 Helper 函数。我们也可以编写自定义的 Helper 函数。</p>
<h3 id="访问方式-5"><a class="markdown-anchor" href="#访问方式-5">#</a> 访问方式</h3>
<p>通过 <code>ctx.helper</code> 访问到 helper 对象，例如：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 假设在 app/router.js 中定义了 home router</span></span><br><span class="line">app.get(<span class="string">'home'</span>, <span class="string">'/'</span>, <span class="string">'home.index'</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用 helper 计算指定 url path</span></span><br><span class="line">ctx.helper.pathFor(<span class="string">'home'</span>, &#123; <span class="attr">by</span>: <span class="string">'recent'</span>, <span class="attr">limit</span>: <span class="number">20</span> &#125;)</span><br><span class="line"><span class="comment">// =&gt; /?by=recent&amp;limit=20</span></span><br></pre></td></tr></table></figure>
<h3 id="扩展方式-5"><a class="markdown-anchor" href="#扩展方式-5">#</a> 扩展方式</h3>
<p>框架会把 <code>app/extend/helper.js</code> 中定义的对象与内置 <code>helper</code> 的 prototype 对象进行合并，在处理请求时会基于扩展后的 prototype 生成 <code>helper</code> 对象。</p>
<p>例如，增加一个 <code>helper.foo()</code> 方法：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app/extend/helper.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  foo(param) &#123;</span><br><span class="line">    <span class="comment">// this 是 helper 对象，在其中可以调用其他 helper 方法</span></span><br><span class="line">    <span class="comment">// this.ctx =&gt; context 对象</span></span><br><span class="line">    <span class="comment">// this.app =&gt; application 对象</span></span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h2 id="按照环境进行扩展"><a class="markdown-anchor" href="#按照环境进行扩展">#</a> 按照环境进行扩展</h2>
<p>另外，还可以根据环境进行有选择的扩展，例如，只在 unittest 环境中提供 <code>mockXX()</code> 方法以便进行 mock 方便测试。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app/extend/application.unittest.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  mockXX(k, v) &#123;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>这个文件只会在 unittest 环境加载。</p>
<p>同理，对于 Application，Context，Request，Response，Helper 都可以使用这种方式针对某个环境进行扩展，更多参见<a href="./env.html">运行环境</a>。</p>

  </article>
  <aside id="mobileAside" class="toc">
  <div class="mobile-menu">
    <ul>
      <li><a href="/zh-cn/intro/" alt="指南">指南</a></li><li><a href="/api/" alt="API">API</a></li><li><a href="/zh-cn/tutorials/index.html" alt="教程">教程</a></li><li><a href="https://github.com/search?q=topic%3Aegg-plugin&type=Repositories" alt="插件">插件</a></li><li><a href="https://github.com/eggjs/egg/releases" alt="发布日志">发布日志</a></li>
      
      
        <li class="translations">
          <a class="nav-link">Translations</a>
          <span class="arrow"></span><ul id="dropdownContent" class="dropdown-content"><li><a id="en" href="/en/basics/extend.html" >English</a></li><li><a id="zh-cn" href="/zh-cn/basics/extend.html" style="color: #22ab28">中文</a></li></ul>
        </li>
      
    </ul>
  </div>
  <dl><dt id="title-Intro" style="cursor: pointer;" class="aside-title">新手指南<a id="collapse-icon-Intro" class="icon opend"></a></dt><dd id=panel-Intro><ul><li><a href="/zh-cn/intro/index.html" class="menu-link">Egg.js 是什么?</a></li><li><a href="/zh-cn/intro/egg-and-koa.html" class="menu-link">Egg.js 和 Koa</a></li><li><a href="/zh-cn/intro/quickstart.html" class="menu-link">快速入门</a></li><li><a href="/zh-cn/tutorials/progressive.html" class="menu-link">渐进式开发</a></li><li><a href="/zh-cn/migration.html" class="menu-link">2.x 升级指南</a></li></ul></dd><dt id="title-Basics" style="cursor: pointer;" class="aside-title">基础功能<a id="collapse-icon-Basics" class="icon opend"></a></dt><dd id=panel-Basics><ul><li><a href="/zh-cn/basics/structure.html" class="menu-link">目录结构</a></li><li><a href="/zh-cn/basics/objects.html" class="menu-link">内置对象</a></li><li><a href="/zh-cn/basics/env.html" class="menu-link">运行环境</a></li><li><a href="/zh-cn/basics/config.html" class="menu-link">配置</a></li><li><a href="/zh-cn/basics/middleware.html" class="menu-link">中间件</a></li><li><a href="/zh-cn/basics/router.html" class="menu-link">Router</a></li><li><a href="/zh-cn/basics/controller.html" class="menu-link">Controller</a></li><li><a href="/zh-cn/basics/service.html" class="menu-link">Service</a></li><li><a href="/zh-cn/basics/plugin.html" class="menu-link">插件</a></li><li><a href="/zh-cn/basics/schedule.html" class="menu-link">定时任务</a></li><li><a href="/zh-cn/basics/extend.html" class="menu-link">框架扩展</a></li><li><a href="/zh-cn/basics/app-start.html" class="menu-link">启动自定义</a></li></ul></dd><dt id="title-Core" style="cursor: pointer;" class="aside-title">核心功能<a id="collapse-icon-Core" class="icon opend"></a></dt><dd id=panel-Core><ul><li><a href="/zh-cn/core/development.html" class="menu-link">本地开发</a></li><li><a href="/zh-cn/core/unittest.html" class="menu-link">单元测试</a></li><li><a href="/zh-cn/core/deployment.html" class="menu-link">应用部署</a></li><li><a href="/zh-cn/core/logger.html" class="menu-link">日志</a></li><li><a href="/zh-cn/core/httpclient.html" class="menu-link">HttpClient</a></li><li><a href="/zh-cn/core/cookie-and-session.html" class="menu-link">Cookie and Session</a></li><li><a href="/zh-cn/core/cluster-and-ipc.html" class="menu-link">多进程模型和进程间通讯</a></li><li><a href="/zh-cn/core/view.html" class="menu-link">模板渲染</a></li><li><a href="/zh-cn/core/error-handling.html" class="menu-link">异常处理</a></li><li><a href="/zh-cn/core/security.html" class="menu-link">安全</a></li><li><a href="/zh-cn/core/i18n.html" class="menu-link">国际化</a></li></ul></dd><dt id="title-Tutorials" style="cursor: pointer;" class="aside-title">教程<a id="collapse-icon-Tutorials" class="icon opend"></a></dt><dd id=panel-Tutorials><ul><li><a href="/zh-cn/tutorials/mysql.html" class="menu-link">MySQL</a></li><li><a href="/zh-cn/tutorials/restful.html" class="menu-link">RESTful API</a></li><li><a href="/zh-cn/tutorials/passport.html" class="menu-link">Passport 鉴权</a></li><li><a href="/zh-cn/tutorials/socketio.html" class="menu-link">Socket.IO</a></li><li><a href="/zh-cn/tutorials/assets.html" class="menu-link">静态资源</a></li><li><a href="/zh-cn/tutorials/typescript.html" class="menu-link">TypeScript</a></li></ul></dd><dt id="title-Advanced" style="cursor: pointer;" class="aside-title">进阶<a id="collapse-icon-Advanced" class="icon opend"></a></dt><dd id=panel-Advanced><ul><li><a href="/zh-cn/advanced/loader.html" class="menu-link">Loader</a></li><li><a href="/zh-cn/advanced/plugin.html" class="menu-link">插件开发</a></li><li><a href="/zh-cn/advanced/framework.html" class="menu-link">框架开发</a></li><li><a href="/zh-cn/advanced/cluster-client.html" class="menu-link">多进程研发模式增强</a></li><li><a href="/zh-cn/advanced/view-plugin.html" class="menu-link">模板插件开发规范</a></li><li><a href="/zh-cn/style-guide.html" class="menu-link">代码风格指南</a></li></ul></dd><dt id="title-Community" style="cursor: pointer;" class="aside-title">社区<a id="collapse-icon-Community" class="icon opend"></a></dt><dd id=panel-Community><ul><li><a href="/zh-cn/plugins/" class="menu-link">内置插件列表</a></li><li><a href="/zh-cn/contributing.html" class="menu-link">如何贡献</a></li><li><a href="/zh-cn/resource.html" class="menu-link">资源</a></li><li><a href="/zh-cn/faq.html" class="menu-link">常见问题</a></li></ul></dd></dl>
</aside>
<script>
var mobileTrigger = document.getElementById('mobileTrigger');
var mobileAside = document.getElementById('mobileAside');

var expandMenu = function(title) {
  // handle icon
  const collapseIcon = document.getElementById('collapse-icon-' + title);
  if (collapseIcon) {
    collapseIcon.className = 'icon opend';
  }
  // handle panelEle
  const panelEle = document.getElementById('panel-' + title);
  if (panelEle) {
    panelEle.className = '';
  }
}

var collapseMenu = function(title) {
  // handle icon
  const collapseIcon = document.getElementById('collapse-icon-' + title);
  if (collapseIcon) {
    collapseIcon.className = 'icon closed';
  }
  // handle panelEle
  const panelEle = document.getElementById('panel-' + title);
  if (panelEle) {
    panelEle.className = 'aside-panel-hidden';
  }
}

mobileAside.onclick = function(e) {
  const targetId = e.target.id;
  if (targetId && (targetId.indexOf('title-') > -1 || targetId.indexOf('collapse-icon-') > -1)) {
    const title = targetId.replace('title-', '').replace('collapse-icon-', '');
    try { 
      // the the browser may have no localStroage or JSON.parse may throw exception.
      const menuInfo = JSON.parse(window.localStorage.getItem('menuInfo'));
        
      // current menu status
      const curClosed = menuInfo[title] ? menuInfo[title].closed : false; // default false

      // change UI
      curClosed ? expandMenu(title) : collapseMenu(title);

      // save menuInfo to localStorage
      menuInfo[title] = { closed: !curClosed } // opposite
      window.localStorage.setItem('menuInfo', JSON.stringify(menuInfo));
    } catch (e) {}
  }
};

mobileTrigger.onclick = function(e) {
  e.preventDefault();
  if (mobileAside.className.indexOf('mobile-show') === -1) {
    mobileAside.className += ' mobile-show';
  } else {
    mobileAside.className = 'toc';
  }
};

(function() {
  // save data to localStorage because the page will refresh when user change the url.
  let menuInfo;
  try { 
    // the the browser may have no localStroage or JSON.parse may throw exception.
    menuInfo = JSON.parse(window.localStorage.getItem('menuInfo'));
    if (!menuInfo) {
      menuInfo = {};
      window.localStorage.setItem('menuInfo', JSON.stringify(menuInfo));
    }
  } catch (e) {
    menuInfo = {}; // default {}
  }

  for (const title in menuInfo) {
    if (menuInfo[title] && menuInfo[title].closed) { // menu in closed status.
      collapseMenu(title);
    } else {
      expandMenu(title);
    }
  }

  // highlight menu
  const pathname = window.location.pathname;
  const selector = `a[href="${pathname}"].menu-link,a[href="${pathname}index.html"].menu-link`;
  const menuItem = mobileAside.querySelector(selector);
  if (menuItem) { menuItem.className += ' highlight'; }
})();
</script>

</div>

  </div>
</body>
<script src="https://cdn.jsdelivr.net/docsearch.js/2/docsearch.min.js"></script>
<script>
docsearch({
  apiKey: '1561de31a86f79507ea00cdb54ce647c',
  indexName: 'eggjs',
  inputSelector: '#search-query',
});
</script>
<div class="cnzz">
<script src="https://s11.cnzz.com/z_stat.php?id=1261142226&web_id=1261142226" language="JavaScript"></script>
</div>

</html>
